import { Component, TemplateRef, ViewChild, ChangeDetectionStrategy, ChangeDetectorRef, Input } from '@angular/core';
import { ComponentFixture, fakeAsync, TestBed, tick, waitForAsync } from '@angular/core/testing';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';
import { MessageOptions } from './interface';
import { MessageModule } from './message.module';
import { MessageService } from './message.service';

@Component({
  template: `
    <button id="default" lv-button (click)="default()">Default</button>
    <button id="info" lv-button (click)="info()">Info</button>
    <button id="success" lv-button (click)="success()">Success</button>
    <button id="warning" lv-button (click)="warning()">Warning</button>
    <button id="error" lv-button (click)="error()">Error</button>

    <ng-template #contentTpl>
      <h2>It's a message.</h2>
    </ng-template>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
class TestMessageComponent {
  @Input() lvMessageKey: string;
  @Input() lvType: 'success' | 'info' | 'warning' | 'error' | 'default' = 'default';
  @Input() lvTitle: string;
  @Input() lvContent: string | TemplateRef<void>;
  @Input() lvRenderType: 'html' | 'text' = 'html';
  @Input() lvWidth: number | string = '360px';
  @Input() lvMaxWidth: number | string = 'auto';
  @Input() lvDuration: number = 0;
  @Input() lvKeepShowOnHover: boolean = true;
  @Input() lvShowCloseButton: boolean = true;
  @Input() lvShowIcon: boolean = true;
  @Input() lvPosition: 'topLeft' | 'topCenter' | 'topRight' = 'topCenter';
  @Input() lvOffset: { x: number; y: number } = { x: 0, y: 0 };
  @Input() lvContextData: number | string | boolean | Array<any> | { [key: string]: any };
  @Input() lvOnDestroy: () => void | {};
  @Input() lvOnShow: () => void | {};

  @ViewChild('contentTpl', { static: true }) contentTpl: TemplateRef<any>;

  constructor(private message: MessageService) {}

  info(): void {
    this.message.info(`It's a message.'`, { lvMessageKey: this.lvMessageKey });
  }

  success(): void {
    this.message.success(`It's a message.'`, { lvMessageKey: this.lvMessageKey });
  }

  warning(): void {
    this.message.warning(`It's a message.'`, { lvMessageKey: this.lvMessageKey });
  }

  error(): void {
    this.message.error(`It's a message.'`, { lvMessageKey: this.lvMessageKey });
  }

  default(): void {
    this.message.create({
      lvMessageKey: this.lvMessageKey,
      lvType: this.lvType,
      lvTitle: this.lvTitle,
      lvContent: this.lvContent,
      lvRenderType: this.lvRenderType,
      lvWidth: this.lvWidth,
      lvMaxWidth: this.lvMaxWidth,
      lvDuration: this.lvDuration,
      lvKeepShowOnHover: this.lvKeepShowOnHover,
      lvShowCloseButton: this.lvShowCloseButton,
      lvShowIcon: this.lvShowIcon,
      lvPosition: this.lvPosition,
      lvOffset: this.lvOffset,
      lvContextData: this.lvContextData,
      lvOnDestroy: this.lvOnDestroy,
      lvOnShow: this.lvOnShow,
    });
  }

  destory(id?: string): void {
    this.message.destroy(id);
  }

  update(id: string, options: MessageOptions): void {
    this.message.update(id, options);
  }

  onShow = (): void => {};

  onDestroy = (): void => {};
}

describe('MessageComponent', () => {
  beforeEach(waitForAsync(() => {
    TestBed.configureTestingModule({
      declarations: [TestMessageComponent],
      imports: [MessageModule, NoopAnimationsModule],
      teardown: { destroyAfterEach: true },
    }).compileComponents();
  }));
  let component: TestMessageComponent;
  let fixture: ComponentFixture<TestMessageComponent>;
  let cdr: ChangeDetectorRef;

  const getMessageBox = () => document.querySelector('lv-message');

  const clearDomNodes = () => {
    document.body.innerHTML = '';
  };

  describe('render', () => {
    const detectChanges = fakeAsync(() => {
      fixture.detectChanges();
      tick(500);
    });

    const openMessage = (id: string = 'default', isContainer: boolean = false) => {
      const button = fixture.nativeElement.querySelector(`#${id}`);
      button.dispatchEvent(new Event('click'));
      detectChanges();
      if (!isContainer) {
        expect(getMessageBox()).toMatchSnapshot();
      } else {
        expect(document.querySelector('.lv-message-container')).toMatchSnapshot();
      }
    };

    beforeEach(() => {
      fixture = TestBed.createComponent(TestMessageComponent);
      component = fixture.componentInstance;
      cdr = fixture.debugElement.injector.get(ChangeDetectorRef);
      component.lvContent = `It's a message.`;
    });

    afterEach(() => {
      clearDomNodes();
    });

    it('with type', () => {
      openMessage('info');

      openMessage('success');

      openMessage('warning');

      openMessage('error');
    });

    it('with lvTitle', () => {
      component.lvTitle = `It's a title`;
      openMessage();
    });

    it('with lvContent of Template', () => {
      component.lvContent = component.contentTpl;
      cdr.detectChanges();
      openMessage();
    });

    it('without lvShowIcon', () => {
      component.lvType = 'info';
      component.lvShowIcon = false;
      openMessage();
    });

    it('without lvShowCloseButton', () => {
      component.lvShowCloseButton = false;
      openMessage();
    });

    it('with lvWidth', () => {
      component.lvWidth = 200;
      openMessage();
    });

    it('with lvPosition', () => {
      component.lvPosition = 'topRight';
      openMessage('default', true);

      component.destory();
      detectChanges();
      component.lvPosition = 'topLeft';
      openMessage('default', true);
    });

    it('with lvOffset', () => {
      component.lvOffset = { x: -20, y: -20 };
      openMessage();
    });
  });

  describe('works', () => {
    const openMessage = (id: string = 'default') => {
      const button = fixture.nativeElement.querySelector(`#${id}`);
      button.dispatchEvent(new Event('click'));
    };

    const close = () => {
      const button = document.querySelector('.lv-message-close-button > i');
      button.dispatchEvent(new Event('click'));
    };

    const detectChanges = fakeAsync(() => {
      cdr.detectChanges();
      tick(500);
    });

    beforeEach(() => {
      fixture = TestBed.createComponent(TestMessageComponent);
      component = fixture.componentInstance;
      cdr = fixture.debugElement.injector.get(ChangeDetectorRef);
      component.lvContent = `It's a message.`;
    });

    afterEach(() => {
      clearDomNodes();
    });

    it('with destory', () => {
      openMessage('info');
      detectChanges();
      openMessage('success');
      component.lvMessageKey = 'errorMessage';
      detectChanges();
      openMessage('error');

      component.destory('errorMessage');
      detectChanges();
      expect(document.querySelector('.lv-message-information')).toBeTruthy();
      expect(document.querySelector('.lv-message-success')).toBeTruthy();
      expect(document.querySelector('.lv-message-error')).toBeNull();

      component.destory();
      detectChanges();
      expect(document.querySelector('.lv-message-information')).toBeNull();
      expect(document.querySelector('.lv-message-success')).toBeNull();
    });

    it('with close button', () => {
      openMessage();
      detectChanges();
      expect(getMessageBox()).toBeTruthy();
      setTimeout(() => {
        close();
        detectChanges();
        expect(getMessageBox()).toBeNull();
      }, 1000);
    });

    it('with lvRenderType', () => {
      component.lvContent = `<h3>It's a message.</h3>`;
      openMessage();
      detectChanges();
      expect(getMessageBox().querySelector('h3')).toBeTruthy();

      component.destory();
      detectChanges();
      component.lvRenderType = 'text';
      openMessage();
      expect(getMessageBox().querySelector('h3')).toBeNull();
    });

    it('with lvDuration', fakeAsync(() => {
      component.lvDuration = 2000;
      openMessage();
      tick(500);
      fixture.detectChanges();
      expect(getMessageBox()).toBeTruthy();

      tick(1500);
      cdr.detectChanges();
      expect(getMessageBox()).toBeNull();
    }));

    it('with lvKeepShowOnHover', fakeAsync(() => {
      component.lvDuration = 2000;
      openMessage();
      tick(500);
      fixture.detectChanges();
      expect(getMessageBox()).toBeTruthy();

      const content = document.querySelector('.lv-message');
      content.dispatchEvent(new Event('mouseenter'));

      tick(1500);
      cdr.detectChanges();
      expect(getMessageBox()).toBeTruthy();

      content.dispatchEvent(new Event('mouseleave'));
      tick(1500);
      cdr.detectChanges();
      tick(1500);
      cdr.detectChanges();
      expect(getMessageBox()).toBeNull();
    }));

    it('with lvOnShow & lvDestory', () => {
      jest.spyOn(component, 'onShow');
      jest.spyOn(component, 'onDestroy');
      component.lvOnShow = component.onShow;
      component.lvOnDestroy = component.onDestroy;

      detectChanges();
      openMessage();
      detectChanges();
      expect(component.onShow).toHaveBeenCalledTimes(1);

      component.destory();
      detectChanges();
      expect(component.onDestroy).toHaveBeenCalledTimes(1);
    });
  });
});
